# ==============================================================================
#
# unixtools.pm
#
# Account maint UNIX accounts modules
#
# $Id: unixtools.pm,v 1.14 2008/04/04 21:53:16 dyoung2 Exp $
#
# Darren Young [darren.young@gsb.uchicago.edu]
#
# ==============================================================================
#
# ChangeLog
#
# $Log: unixtools.pm,v $
# Revision 1.14  2008/04/04 21:53:16  dyoung2
#   * Changes for registrar in testing.
#
# Revision 1.13  2005/08/29 18:29:52  dyoung2
#   * Added _getUNIXShellForUser().
#   * Added test to see if the current shell and the new shell are the same.
#
# Revision 1.12  2005/08/29 18:16:46  dyoung2
#   * Changed the return for _setUNIXGroupIdForUser to be SHELL_TRUE
#     in the event the user's group is already correct. It now skips that
#     step and issues a warning message in the event the user account is
#     already correct.
#
# Revision 1.11  2005/08/26 21:30:26  dyoung2
#   * Changed over to use _setPwValByName.
#
# Revision 1.10  2005/08/26 20:23:44  dyoung2
#   * Fixe regexp selector in _setPwValByName.
#
# Revision 1.9  2005/08/26 20:10:09  dyoung2
#   * Added _setPwValByName.
#
# Revision 1.8  2005/08/25 22:38:27  dyoung2
#   * Standardized BEGIN headers.
#
# Revision 1.7  2005/06/27 16:30:47  dyoung2
#   * Changed -w -e to be just -e in the passwd reads so unpriv users work.
#
# Revision 1.6  2005/06/13 21:09:53  dyoung2
#   * Added _getGroupIdByName() function.
#
# Revision 1.5  2005/06/13 18:04:51  dyoung2
#   * Cleaned up some stray whitespace.
#
# Revision 1.4  2005/06/07 00:29:11  dyoung2
#   * Added _getPwEntByName function.
#
# Revision 1.3  2005/06/07 00:05:33  dyoung2
#   * Changed all login functions to not use getpw* calls so they're
#     platform (and NIS) independent.
#   * Added generic _getPwdValByName function.
#
# Revision 1.2  2005/06/03 22:42:52  dyoung2
#   * Initial version for testing with acctmaint.pl.
#
# Revision 1.1  2005/06/03 16:46:51  dyoung2
#   * Added from template.
#
# ==============================================================================

my  $cvsid   = '$Id: unixtools.pm,v 1.14 2008/04/04 21:53:16 dyoung2 Exp $';
my  @cvsinfo = split( ' ', $cvsid );
our $NAME    = File::Basename::basename($0);
our $VERSION = $cvsinfo[2];

$| = 1; # don't buffer output (auto-flush to true)

# ------------------------------------------------------------------------------
#                             B E G I N
# ------------------------------------------------------------------------------
BEGIN {

    # Pragmas
    use strict;

    # "Standard" modules we use
    use FindBin;
    use Config::Simple;
    use lib "$FindBin::Bin/../lib";

    # Standard account maint modules to use
    use logging;
    use errcodes;
    use utils;
    
    # Modules that this module uses
    use File::Copy;
    use Fcntl qw(:DEFAULT :flock);

}


# ------------------------------------------------------------------------------ 
#                             V A R I A B L E S
# ------------------------------------------------------------------------------ 
our $LOGFILE    = "$FindBin::Bin/../log/acctmgr.log" unless($LOGFILE); 
our $CONFIGFILE = "$FindBin::Bin/../etc/acctmgr.cfg" unless($CONFIGFILE);


# NIS related variables.
# only set locally if they're not set previously
our $AUTOHOME   = $CFG->param("base.nisdir") . "/" . $CFG->param("files.autohome")   
        unless($AUTOHOME);

our $GROUPFILE  = $CFG->param("base.nisdir") . "/" . $CFG->param("files.groupfile")  
        unless($GROUPFILE);

our $PASSWDFILE = $CFG->param("base.nisdir") . "/" . $CFG->param("files.passwdfile") 
        unless($PASSWDFILE);


# ------------------------------------------------------------------------------
#                             C O N F I G   F I L E
# ------------------------------------------------------------------------------
our $CFG = new Config::Simple( filename => $CONFIGFILE ) or die "$!\n" unless($CFG);


# =============================================================================
# NAME        : _getPwdValByName
# DESCRIPTION : return the value of a given password file field
# REQUIRES    : 
# RETURNS     : 
# NOTES       : 
# =============================================================================
sub _getPwValByName {

    my $name = "_getPwValByName()"; 
    my ( $package, $filename, $line ) = caller;  
    debug("$name: entering with args @_"); 
    debug("$name: called from package->$package, filename->$filename, line->$line"); 

    my $pwFile;
    my $pwKey;
    my $pwField;

    ### 2 arg form
    if ( scalar(@_) == 2 ) {
        debug("$name: called as 2 arg form, using default passwd file");
        $pwFile  = $PASSWDFILE; # default passwd file
        $pwKey   = $_[0];
        $pwField = $_[1];

    ### 3 arg form
    } elsif ( scalar(@_) == 3 ) {
        debug("$name: called as 3 arg form");
        $pwFile  = $_[0];
        $pwKey   = $_[1];
        $pwField = $_[2];
    } else {
        logmsg("$name: SYNTAX ERROR in caller");
        return(0);
    }

    ### make sure the password file exists
    if ( ! -e $pwFile ) {
        logmsg("$name: FAILED -> file ($pwFile) does not exist");
        return(0);
    }

    ### make sure they gave us a key to look for
    if ( ! defined($pwKey) ) {
        logmsg("$name: SYNTAX ERROR (no key given to me)");
        return(0);
    }

    ### make sure they gave us the field get the value for
    if ( ! defined($pwField) ) {
        logmsg("$name: SYNTAX ERROR (no field given to me)");
        return(0);
    } 

    ###
    # Basic (NIS) passwd struct
    ###
    my @pwfields = (
                     "username",  # 0 - username
                     "encpass" ,  # 1 - encrypted password
                     "uid"     ,  # 2 - user id
                     "gid"     ,  # 3 - group id
                     "gecos"   ,  # 4 - gecos (usually full name)
                     "homedir" ,  # 5 - home directory
                     "shell"      # 6 - login shell
                   );

    my $hit       = 0;  # default to false
    my $fieldpos  = ""; # the position of the field in the record
    my $fieldname = ""; # the name of the field
    my $retval    = ""; # our return value, default to NULL

    ### if the requested field is a numeric value
    if ( $pwField =~ /[0-9]/ ) {
 
        ### since they gave us a numeric field, make sure it exists in the array
        debug("$name: field -> $pwField -> is_number");
        if ( ! exists($pwfields[$pwField]) ) {
            logmsg("$name: field # $pwField is not valid");
            return(0);
        } else {
            $fieldpos = $pwField;
            $fieldname = $pwfields[$fieldpos];
            debug("$name: fieldpos -> $fieldpos, fieldname -> $fieldname");
        }

    ### otherwise they must have asked for an alpha field name
    } else {
        
        ### since they gave us an alpha field, look up it's position in the array
        $pwField = lc($pwField);  # enforce lower case
        debug("$name: field -> $pwField -> is_alpha");

        ### see if the requested field is valid
        for ( my $ctr = 0; $ctr < scalar(@pwfields); $ctr++ ) {
            if ( $pwfields[$ctr] eq $pwField ) {
                $fieldpos = $ctr;
                $fieldname = $pwfields[$ctr];
                $hit = 1;
                debug("$name: fieldpos -> $fieldpos, fieldname -> $fieldname");
                last; # break on the first one found
            } else {
                $hit = 0; # didn't find anything
                next;
            }
        }

        ### if we didn't find a value, tell someone about it
        if ( ! $hit ) {
            my $ff;
            logmsg("$name: field [$pwField] isn't valid");
            for ( $ctr = 0; $ctr < scalar(@pwfields); $ctr++ )  {
                $ff = $ff . " " . $pwfields[$ctr];
            }
            logmsg("$name: possible values are: $ff");
            return(0);
        }
    }

    ###
    # open the password file for read
    ###
    if ( ! open( PWIN, "<$pwFile" ) ) {
        logmsg("$name: FAILED to open password file for read ($!)");
        return(0);
    }

    ### flip through every line in the file and look for the key
    ### if we find it, set the return to that value
    while ( @line = split(/:/, <PWIN> ) ) {
        chomp(@line);
        if ( $line[0] eq $pwKey ) {
            debug("$name: pwKey -> $pwKey, fieldname -> $fieldname, value -> $line[$fieldpos]");
            $retval = $line[$fieldpos];
            last;
        } 
    }

    ###
    # close the password file
    ###
    if ( ! close(PWIN) ) {
        logmsg("$name: FAILED to close the password file ($!)");
        return(0);
    }

    ### return the requested field value if necessary, otherwise return
    ### an empty string
    if ( $retval ne "" ) {
        debug("$name: returning with val -> $retval");
        return($retval);
    } else {
        debug("$name: FAILED to find entry for pwKey -> $pwKey");
        return("");
    }

}


# =============================================================================
# NAME        : _setPwdValByName
# DESCRIPTION : set the value of a given password file field
# REQUIRES    : 
# RETURNS     : 
# NOTES       : 
# =============================================================================
sub _setPwValByName {

    my $name = "_setPwValByName()"; 
    my ( $package, $filename, $line ) = caller;  
    debug("$name: entering with args @_"); 
    debug("$name: called from package->$package, filename->$filename, line->$line"); 

    my $pwFile;   # password file to operate on
    my $pwKey;    # key to use for update
    my $pwField;  # the field to update
    my $pwVal;    # value to set the field to    

    ### 3 arg form
    if ( scalar(@_) == 3 ) {
        debug("$name: called as 3 arg form, using default passwd file");
        $pwFile  = $PASSWDFILE; # default passwd file
        $pwKey   = $_[0];
        $pwField = $_[1];
        $pwVal   = $_[2];

    ### 4 arg form
    } elsif ( scalar(@_) == 4 ) {
        debug("$name: called as 4 arg form");
        $pwFile  = $_[0];
        $pwKey   = $_[1];
        $pwField = $_[2];
        $pwVal   = $_[3];
    } else {
        logmsg("$name: SYNTAX ERROR in caller");
        return(0);
    }

    ### make sure the password file exists
    if ( ! -e $pwFile ) {
        logmsg("$name: FAILED -> file ($pwFile) does not exist");
        return(0);
    }

    ### make sure they gave us a key to look for
    if ( ! defined($pwKey) ) {
        logmsg("$name: SYNTAX ERROR (no key given to me)");
        return(0);
    }

    ### make sure they gave us the field get the value for
    if ( ! defined($pwField) ) {
        logmsg("$name: SYNTAX ERROR (no field given to me)");
        return(0);
    } 
    
    ### make sure they gave us the field get the value for
    if ( ! defined($pwVal) ) {
        logmsg("$name: SYNTAX ERROR (no value given to me)");
        return(0);
    } 

    ###
    # Basic (NIS) passwd struct
    ###
    my @pwfields = (
                     "username",  # 0 - username
                     "encpass" ,  # 1 - encrypted password
                     "uid"     ,  # 2 - user id
                     "gid"     ,  # 3 - group id
                     "gecos"   ,  # 4 - gecos (usually full name)
                     "homedir" ,  # 5 - home directory
                     "shell"      # 6 - login shell
                   );

    my $hit       = 0;  # default to false
    my $fieldpos  = ""; # the position of the field in the record
    my $fieldname = ""; # the name of the field
    my $retval    = ""; # our return value, default to NULL
    my $tmppwfile = "/tmp/pwd.tmp"; # temp file
    my $bakpwfile = "/tmp/pwd.bak"; # backup file    

    ### if the requested field is a numeric value
    if ( $pwField =~ /[0-9]/ ) {
 
        ### since they gave us a numeric field, make sure it exists in the array
        debug("$name: field -> $pwField -> is_number");
        if ( ! exists($pwfields[$pwField]) ) {
            logmsg("$name: field # $pwField is not valid");
            return(0);
        } else {
            $fieldpos = $pwField;
            $fieldname = $pwfields[$fieldpos];
            debug("$name: fieldpos -> $fieldpos, fieldname -> $fieldname");
        }

    ### otherwise they must have asked for an alpha field name
    } else {
        
        ### since they gave us an alpha field, look up it's position in the array
        $pwField = lc($pwField);  # enforce lower case
        debug("$name: field -> $pwField -> is_alpha");

        ### see if the requested field is valid
        for ( my $ctr = 0; $ctr < scalar(@pwfields); $ctr++ ) {
            if ( $pwfields[$ctr] eq $pwField ) {
                $fieldpos = $ctr;
                $fieldname = $pwfields[$ctr];
                $hit = 1;
                debug("$name: fieldpos -> $fieldpos, fieldname -> $fieldname");
                last; # break on the first one found
            } else {
                $hit = 0; # didn't find anything
                next;
            }
        }

        ### if we didn't find a value, tell someone about it
        if ( ! $hit ) {
            my $ff;
            logmsg("$name: field [$pwField] isn't valid");
            for ( $ctr = 0; $ctr < scalar(@pwfields); $ctr++ )  {
                $ff = $ff . " " . $pwfields[$ctr];
            }
            logmsg("$name: possible values are: $ff");
            return(0);
        }
    }


    ###
    # remove any old versions of the temp working file
    ###
    if ( -f $tmppwfile ) {
        if ( unlink($tmppwfile) ) {
            logmsg("$name: removed previous working passwd file -> $tmppwfile");
        } else {
            logmsg("$name: FAILED to remove working passwd file -> $tmppwfile ($!)");
            return(0);
        }
    }

    ###
    # remove any old versions of the backup file
    ###
    if ( -f $bakpwfile ) {
        if ( unlink($bakpwfile) ) {
            logmsg("$name: removed previous backup passwd file -> $bakpwfile");
        } else {
            logmsg("$name: FAILED to remove backup passwd file -> $bakpwfile ($!)");
            return(0);
        }
    }    
    
    ###
    # open the password file for read
    ###
    if ( ! open( PWIN, "<$pwFile" ) ) {
        logmsg("$name: FAILED to open password file for read ($!)");
        return(0);
    }

    ### flip through every line in the file and shove it in a work array
    my @pwin;   # working input array
    while(<PWIN>) {
        chomp(); # remove trailing \n
        push( @pwin, $_ );
    }

    ###
    # close the password file
    ###
    if ( ! close(PWIN) ) {
        logmsg("$name: FAILED to close the password file ($!)");
        return(0);
    }
    
    debug("$name: read " . scalar(@pwin) . " entries from password file");

    # flip through all of the password entries in the input array
    # if we find a match, change the desired field
    # then, flip through all the possible fields and build a new password line for that
    # account. Otherwise, just pop the old line onto a new output array
    my @pwout;  # working output array
    foreach my $entry ( @pwin ) {
        my @line = split(/:/, $entry);
        if ( $line[0] =~ /^$pwKey$/ ) {  
            debug("$name: pwKey -> $pwKey, fieldname -> $fieldname, current value -> $line[$fieldpos]");            
            
            # change the requested field to the requested value
            $line[$fieldpos] = $pwVal;
            debug("$name: pwKey -> $pwKey, fieldname -> $fieldname, new value -> $line[$fieldpos]");
            
            # build the new password entry
            my $new = "";
            for ( my $ctr = 0; $ctr < scalar(@pwfields); $ctr++ ) {
                if ( $ctr == 0 ) {
                    $new = $line[$ctr];
                } else {
                    $new = $new . ":" . $line[$ctr];
                }
            }
            
            # push the newly formatted password entry onto the output array
            push(@pwout, $new);
            next;
            
        } else {
            
            # otherwise, we're working on the other entries so just push them onto
            # the output array
            push(@pwout, $entry);
            
        }
    }
    
    
    # make sure the count of the input array matches the count in the output array
    if ( scalar(@pwin) == scalar(@pwout) ) {
        debug("$name: input and output array counts match");
    } else {
        logmsg("$name: FAILED to make change, input and output counts don't match");
        return(0);
    }
    
    ###
    # open the temp output passwd file for write
    ###
    if ( open( TMPPWFILE, ">", $tmppwfile ) ) {
        debug("$name: opened tmp password file for write");

        ###
        # Place an exclusive lock on the tmp passwd file
        ###
        if ( flock( TMPPWFILE, LOCK_EX ) ) {
            debug("$name: placed exclusive lock on tmp passwd file");
        } else {
            logmsg("$name: FAILED to place lock on tmp passwd file ($!)");
            return(0);
        }

    } else {
        logmsg("$name: FAILED to open tmp passwd file for write ($!)");
        return(0);
    }
    
    
    ###
    # write it all out to the temp file
    ###
    foreach my $pwentry (@pwout)  {
        print TMPPWFILE $pwentry . "\n";
    }


    ###
    # unlock the tmp passwd file
    ###
    if ( flock( TMPPWFILE, LOCK_UN ) ) {
        debug("$name: removed lock from tmp passwd file");
    } else {
        logmsg("$name: FAILED to remove lock from tmp passwd file ($!)");
        return(0);
    }


    ###
    # close the tmp passwd file
    ###
    if ( close(TMPPWFILE) ) {
        debug("$name: closed tmp passwd file");
    } else {
        logmsg("$name: FAILED to close tmp passwd file ($!)");
        return(0);
    }
    
    
    ###
    # make a backup of the live file 
    ###
    if ( copy($pwFile, $bakpwfile) ) {
        debug("$name: copied live passwd file to backup file -> $bakpwfile");
    } else {
        logmsg("$name: FAILED to copy live passwd file to backup file -> $bakpwfile ($!)");
        return(0);
    }
    
    
    ###
    # if we made it this far, take the tmp file and move it over to the live one
    ###
    logmsg("$name: removing the live passwd file and moving the new one in place");
    if ( unlink($pwFile) ) {
        debug("$name: file -> $pwFile removed");
        if ( copy($tmppwfile, $pwFile) ) {
            debug("$name: copied modified tmp file -> $tmppwfile into -> $pwFile");
            if ( unlink($tmppwfile) ) {
                debug("$name: removed tmp file -> $tmppwfile");
                if ( unlink($bakpwfile) ) {
                    debug("$name: removed bak file -> $bakpwfile");
                    return(1);
                } else {
                    logmsg("$name: FAILED to remove bak file -> $bakpwfile (NOT REALLY BAD)");
                    return(1);
                }
            } else {
                logmsg("$name: FAILED to remove tmp file -> $tmppwfile (NOT REALLY BAD)");
                return(1);
            }
        } else {
            logmsg("$name: FAILED to copy modified tmp file -> $tmppwfile into -> $pwFile");
            logmsg("$name: restoring from backup");
            if ( copy($bakpwfile, $pwFile)) {
                logmsg("$name: restored file from before this change started");
                return(0);
            } else {
                logmsg("$name: FAILED!!! CRAP!!! all file copies failed!!");
                return(0);
            }
        }
    } else {
        logmsg("$name: FAILED to remove file -> $pwFile ($!)");
        return(0);
    }

}


# =============================================================================
# NAME        : _getGroupIdByName
# DESCRIPTION : return the GID of a named group
# REQUIRES    : optional(string(file)), mandatory(string(name))
# RETURNS     : string(groupId) or NULL
# NOTES       : Can't return FALSE since 0 is a valid GID, instead check for
#             : defined() on the returned value
# =============================================================================
sub _getGroupIdByName {

    my $name = "_getGroupIdByName()"; 
    my ( $package, $filename, $line ) = caller;  
    debug("$name: entering with args @_"); 
    debug("$name: called from package->$package, filename->$filename, line->$line"); 

    my $groupFile;
    my $groupName;
    my $groupId;

    ### 1 arg form
    if ( scalar(@_) == 1 ) {
        debug("$name: called as 2 arg form, using default group file");
        $groupFile = $GROUPFILE; # default group file
        $groupName = $_[0];

    ### 2 arg form
    } elsif ( scalar(@_) == 2 ) {
        debug("$name: called as 3 arg form");
        $groupFile = $_[0];
        $groupName = $_[1];
    } else {
        logmsg("$name: SYNTAX ERROR in caller");
        return("");
    }

    ### make sure the group file exists
    if ( ! -e $groupFile ) {
        logmsg("$name: FAILED -> file ($groupFile) is not writable (or doesn't exist)");
        return("");
    }

    ### make sure they gave us a name to look for
    if ( ! defined($groupName) ) {
        logmsg("$name: SYNTAX ERROR (no group name given to me)");
        return("");
    }

    ###
    # open the group file for read
    ###
    if ( ! open( GROUPIN, "<$groupFile" ) ) {
        logmsg("$name: FAILED to open group file for read ($!)");
        return("");
    }

    ### flip through every line in the file and look for the key
    ### if we find it, set the return to that value
    while ( my @line = split(/:/, <GROUPIN> ) ) {
        chomp(@line);
        if ( $line[0] eq $groupName ) {
            $groupId = $line[2];
            debug("$name: found line groupName -> $groupName, groupId -> $groupId");
            last;
        } 
    }

    ###
    # close the group file
    ###
    if ( ! close( GROUPIN ) ) {
        logmsg("$name: FAILED to close the group file ($!)");
        return("");
    }

    if ( $groupId ) {
        debug("$name: found entry, returning the data");
        return($groupId);
    } else {
        logmsg("$name: FAILED to find an entry for $groupName");
        return("");
    }

}


# =============================================================================
# NAME        : _getPwEntByName
# DESCRIPTION : 
# REQUIRES    : 
# RETURNS     : 
# NOTES       : 
# =============================================================================
sub _getPwEntByName {

    my $name = "_getPwEntByName()"; 
    my ( $package, $filename, $line ) = caller;  
    debug("$name: entering with args @_"); 
    debug("$name: called from package->$package, filename->$filename, line->$line"); 

    my $pwFile;
    my $pwKey;
    my @pwEnt;

    ### 1 arg form
    if ( scalar(@_) == 1 ) {
        debug("$name: called as 2 arg form, using default passwd file");
        $pwFile  = $PASSWDFILE; # default passwd file
        $pwKey   = $_[0];

    ### 2 arg form
    } elsif ( scalar(@_) == 2 ) {
        debug("$name: called as 3 arg form");
        $pwFile  = $_[0];
        $pwKey   = $_[1];
    } else {
        logmsg("$name: SYNTAX ERROR in caller");
        return(0);
    }

    ### make sure the password file exists
    if ( ! -e $pwFile ) {
        logmsg("$name: FAILED -> file ($pwFile) is not writable (or doesn't exist)");
        return(0);
    }

    ### make sure they gave us a key to look for
    if ( ! defined($pwKey) ) {
        logmsg("$name: SYNTAX ERROR (no key given to me)");
        return(0);
    }

    ###
    # open the password file for read
    ###
    if ( ! open( PWIN, "<$pwFile" ) ) {
        logmsg("$name: FAILED to open password file for read ($!)");
        return(0);
    }

    ### flip through every line in the file and look for the key
    ### if we find it, set the return to that value
    while ( my @line = split(/:/, <PWIN> ) ) {
        chomp(@line);
        if ( $line[0] eq $pwKey ) {
            debug("$name: found line pwKey -> $pwKey");
            @pwEnt = @line;
            last;
        } 
    }

    ###
    # close the password file
    ###
    if ( ! close(PWIN) ) {
        logmsg("$name: FAILED to close the password file ($!)");
        return(0);
    }

    if ( @pwEnt ) {
        debug("$name: found entry, returning the array of data");
        return(@pwEnt);
    } else {
        logmsg("$name: FAILED to find an entry for $pwKey");
        return(0);
    }

}


# =============================================================================
# NAME        : _isValidUNIXAccount
# DESCRIPTION : checks the passwd file to see if a user exists or not
# REQUIRES    : string(login)
# RETURNS     : TRUE or FALSE
# NOTES       : This function does NOT return the user's ID since it's 
#             : entirely possible that their ID is 0 (root) and in that case 
#             : would return false (since it's 0).
# =============================================================================
sub _isValidUNIXAccount { 
    my ($login) = @_; 

    my $name = "_isValidUNIXAccount()"; 
    my ( $package, $filename, $line ) = caller;  
    debug("$name: entering with args @_"); 
    debug("$name: called from package->$package, filename->$filename, line->$line"); 

    if ( ! $login ) {
        logmsg("$name: SYNTAX ERROR (no login given to me for checking)");
        return(0);
    }

    my $found = 0;  # default to false

    ### see if the password file contains an entry for the login
    if ( _getPwValByName($login, "uid") ) {
        $found = 1;
    }

    debug("$name: returning with value: $found"); 
    ($found) ? return($found) : return(0); 
} 


# =============================================================================
# NAME        : _getUNIXUserIdForUser
# DESCRIPTION : return the uid for the given login
# REQUIRES    : string(login)
# RETURNS     : string(id) or NULL
# NOTES       : 
# =============================================================================
sub _getUNIXUserIdForUser { 
    my ($login) = @_; 

    my $name = "_getUNIXUserIdForUser()"; 
    my ( $package, $filename, $line ) = caller;  
    debug("$name: entering with args @_"); 
    debug("$name: called from package->$package, filename->$filename, line->$line"); 

    if ( ! $login ) {
        logmsg("$name: SYNTAX ERROR (no login given to me for checking)");
        return(0);
    }

    my $uid = "";
    my $retval = "";  # default to false

    ### make sure the account is valid, if it is, get the passwd struct and
    ### return the uid from it. if not, return an empty string.
    if ( _isValidUNIXAccount($login) ) {
        
        ### grab the uid from the password file for that user
        $uid = _getPwValByName($login, "uid");

        debug("$name: found UID for user $login -> $uid");
        $retval = $uid

    } else {

        logmsg("$name: FAILED to obtain information, user $login is not a valid UNIX account");
        $retval = "";

    }

    debug("$name: returning with value: $retval");
    return($retval);
}


# =============================================================================
# NAME        : _isValidUNIXGroupId
# DESCRIPTION : see if a group is valid based on id
# REQUIRES    : string(gid)
# RETURNS     : TRUE or FALSE
# NOTES       :
# =============================================================================
sub _isValidUNIXGroupId {
    my ($gid) = @_; 

    my $name = "_isValidUNIXGroupId()"; 
    my ( $package, $filename, $line ) = caller;  
    debug("$name: entering with args @_"); 
    debug("$name: called from package->$package, filename->$filename, line->$line"); 

    if ( ! $gid ) {
        logmsg("$name: SYNTAX ERROR (no group id given to me for checking)");
        return(0);
    }

    my $found = 0;  # default to false

    ## reset the group read "pointer", see man setpwent(5) for details
    if ( setgrent() ) {
        debug("$name: resetting group file pointer");
    } else {
        logmsg("$name: FAILED to set group file pointer");
        return(0);
    }
       
    ### use the system call getgrid to see if the group is valid or not
    ### this is done because the call is nsswitch/pam aware
    if ( getgrgid($gid) ) {
        my ($grpname, $grppwd, $grpid, $grpmembers) = getgrgid($gid);
        debug("$name: found group entry for group id $gid ($grpname)");
        $found = 1; 
    } else {
        logmsg("$name: did NOT find group entry for group id $gid");
        $found = 0;
    }
 
    debug("$name: returning with value: $found"); 
    ($found) ? return($found) : return(0); 
}


# =============================================================================
# NAME        : _getUNIXGroupIdForUser
# DESCRIPTION : retrieve the primary grup id (GID) for a given login account
# REQUIRES    : string(login)
# RETURNS     : string(gid) or NULL
# NOTES       : 
# =============================================================================
sub _getUNIXGroupIdForUser {
    my ($login) = @_; 

    my $name = "_getUNIXGroupIdForUser()"; 
    my ( $package, $filename, $line ) = caller;  
    debug("$name: entering with args @_"); 
    debug("$name: called from package->$package, filename->$filename, line->$line"); 

    if ( ! $login ) {
        logmsg("$name: SYNTAX ERROR (no login given to me for checking)");
        return(0);
    }

    my $retval;
    my $gid;

    ### make sure the account is valid, if it is, get the passwd struct and
    ### return the gid from it. if not, return an empty string.
    if ( _isValidUNIXAccount($login) ) {

        $gid = _getPwValByName($login, "gid");
         
        debug("$name: found GID for user $login -> $gid");
        $retval = $gid;

    } else {

        logmsg("$name: FAILED to obtain information, user $login is not a valid UNIX account");
        $retval = "";

    }

    debug("$name: returning with value: $retval");
    return($retval);
}


# =============================================================================
# NAME        : _getUNIXShellForUser
# DESCRIPTION : retrieve the login shell for a given login account
# REQUIRES    : string(login)
# RETURNS     : string(gid) or NULL
# NOTES       : 
# =============================================================================
sub _getUNIXShellForUser {
    my ($login) = @_; 

    my $name = "_getUNIXShellForUser()"; 
    my ( $package, $filename, $line ) = caller;  
    debug("$name: entering with args @_"); 
    debug("$name: called from package->$package, filename->$filename, line->$line"); 

    if ( ! $login ) {
        logmsg("$name: SYNTAX ERROR (no login given to me for checking)");
        return(0);
    }

    my $retval;
    my $shell;

    ### make sure the account is valid, if it is, get the passwd struct and
    ### return the gid from it. if not, return an empty string.
    if ( _isValidUNIXAccount($login) ) {

        $shell = _getPwValByName($login, "shell");
         
        debug("$name: found SHELL for user $login -> $shell");
        $retval = $shell;

    } else {

        logmsg("$name: FAILED to obtain information, user $login is not a valid UNIX account");
        $retval = "";

    }

    debug("$name: returning with value: $retval");
    return($retval);
}


# =============================================================================
# NAME        : _setUNIXGroupIdForUser
# DESCRIPTION : change the primary group id for a given user in a passwd file
# REQUIRES    : string(pwfile), string(login), string(newgid)
# RETURNS     : TRUE or FALSE
# NOTES       : 
# =============================================================================
sub _setUNIXGroupIdForUser {
    my ( $pwFile ,  # password file to operate on
         $login  ,  # username to update
         $newgid    # new gid for that user
       ) = @_; 

    my $name = "_setUNIXGroupIdForUser()"; 
    my ( $package, $filename, $line ) = caller; 
    debug("$name: entering with args @_");
    debug("$name: called from package->$package, filename->$filename, line->$line");
    
    ### 2 arg form
    if ( scalar(@_) == 2 ) {
        debug("$name: called as 2 arg form, using default passwd file");
        $pwFile  = $PASSWDFILE; # default passwd file
        $login   = $_[0];
        $newgid  = $_[1];

    ### 3 arg form
    } elsif ( scalar(@_) == 3 ) {
        debug("$name: called as 3 arg form");
        $pwFile  = $_[0];
        $login   = $_[1];
        $newgid  = $_[2];
    } else {
        logmsg("$name: SYNTAX ERROR in caller");
        return(0);
    }    


    # make sure they give us a login to change, and if they do
    # make sure it's a valid UNIX account
    if ( ! $login ) {
        logmsg("$name: SYNTAX ERROR (no login given to me)");
        return(0);
    } else {
        if ( ! _isValidUNIXAccount($login) ) {
            logmsg("$name: FAILED -> login $login is not a valid UNIX account");
            return(0);
        }
    }

    # make sure they give us a new gid to change the user's account to, and if they do
    # make sure it's a valid group id
    if ( ! $newgid ) {
        logmsg("$name: SYNTAX ERROR (no new group id given to me)");
        return(0);
    } else {
        if ( ! _isValidUNIXGroupId($newgid) ) {
            logmsg("$name: FAILED -> group id $newgid is not a valid UNIX group");
            return(0);
        }
    }

    # make sure the current gid and the requested new gid are NOT the same
    logmsg("$name: checking current gid against requested new gid");
    if ( my $curgid = _getUNIXGroupIdForUser($login) eq $newgid ) {
        logmsg("$name: ** WARNING ** Current gid and new gid values are the same");
        return(PERL_TRUE);
    }
    
    ## set the primary gid in the password file
    if ( _setPwValByName($pwFile, $login, "gid", $newgid) ) {
        debug("$name: SUCCESSFULLY changed GID to $newgid");
        return(1);
    } else {
        logmsg("$name: FAILED to change GID to $newgid");
        return(0);
    }

}


# =============================================================================
# NAME        : _setUNIXShellForUser
# DESCRIPTION : change the login shell for a user in the password file
# REQUIRES    : string(pwfile), string(login), string(shell)
# RETURNS     : TRUE or FALSE
# NOTES       : 
# =============================================================================
sub _setUNIXShellForUser {
    my ( $pwFile ,  # password file to operate on
         $login  ,  # username to update
         $newshell  # new gid for that user
       ) = @_; 

    my $name = "_setUNIXShellForUser()"; 
    my ( $package, $filename, $line ) = caller; 
    debug("$name: entering with args @_");
    debug("$name: called from package->$package, filename->$filename, line->$line");
    
    ### 2 arg form
    if ( scalar(@_) == 2 ) {
        debug("$name: called as 2 arg form, using default passwd file");
        $pwFile   = $PASSWDFILE; # default passwd file
        $login    = $_[0];
        $newshell = $_[1];

    ### 3 arg form
    } elsif ( scalar(@_) == 3 ) {
        debug("$name: called as 3 arg form");
        $pwFile   = $_[0];
        $login    = $_[1];
        $newshell = $_[2];
    } else {
        logmsg("$name: SYNTAX ERROR in caller");
        return(0);
    }    


    # make sure they give us a login to change, and if they do
    # make sure it's a valid UNIX account
    if ( ! $login ) {
        logmsg("$name: SYNTAX ERROR (no login given to me)");
        return(0);
    } else {
        if ( ! _isValidUNIXAccount($login) ) {
            logmsg("$name: FAILED -> login $login is not a valid UNIX account");
            return(0);
        }
    }


    # make sure they give us a new shell to change the user's account to
    if ( ! $newshell ) {
        logmsg("$name: SYNTAX ERROR (no new shell given to me)");
        return(0);
    }
    
    
    # make sure the current shell and the requested new shell are NOT the same
    logmsg("$name: checking current shell against requested new shell");
    if ( my $curshell = _getUNIXShellForUser($login) eq $newshell ) {
        logmsg("$name: ** WARNING ** Current shell and new shell values are the same");
        return(PERL_TRUE);
    }
    
    
    ## set the primary shell in the password file
    if ( _setPwValByName($pwFile, $login, "shell", $newshell) ) {
        debug("$name: SUCCESSFULLY changed SHELL to $newshell");
        return(1);
    } else {
        logmsg("$name: FAILED to change SHELL to $newshell");
        return(0);
    }

}

# =============================================================================
# NAME        :
# DESCRIPTION :
# REQUIRES    :
# RETURNS     :
# NOTES       :
# =============================================================================
sub _add_yppasswd {
    my ( $uname, $encpassword, $uid, $gid, $fullname, $homedir, $shell ) = @_;
    my $name = "_add_yppasswd";
    my ( $package, $filename, $line ) = caller;
    logmsg("$name: entering with args @_");
    logmsg( "$name: called from package->$package, filename->$filename, line->$line" );

    # append the username on to /home
    $homedir .= "/$uname";
    logmsg("$name: homedir is now -> $homedir");

    # replace _'s with spaces
    $fullname =~ tr/\_/ /;
    logmsg("$name: fullname is now -> $fullname");

    logmsg("$name: about to append to password file (file => $PASSWDFILE)");
    my $skip = 0;

    # Open the password file for append
    if ( open( FO, ">>$PASSWDFILE" ) ) {
        logmsg("$name: opened password file for append");
    } else {
        logmsg("$name: FAILED to open password file for append ($!)");
        $skip = 1;
    }

    # Place an exclusive lock on the file
    if ( flock( FO, LOCK_EX ) ) {
        logmsg("$name: placed exclusive lock on password file");
    } else {
        logmsg("$name: FAILED to place lock on file ($!)");
        $skip = 1;
    }

    # Print the new record to the file
    logmsg("$name: adding entry to passwd file");
    print FO "$uname:$encpassword:$uid:$gid:$fullname:$homedir:$shell\n";

    # unlock the file
    if ( flock( FO, LOCK_UN ) ) {
        logmsg("$name: removed lock from password file");
    } else {
        logmsg("$name: FAILED to remove lock from password file ($!)");
        $skip = 1;
    }

    # close the file
    if ( close(FO) ) {
        logmsg("$name: closed password file");
    } else {
        logmsg("$name: FAILED to close password file ($!)");
        $skip = 1;
    }

    # if something died, return false
    if ($skip) {
        logmsg("$name: something failed during processing, returning 0");
        return (0);
    } else {
        logmsg("$name: processing successfull, returning 1");
        return (1);
    }
}

# =============================================================================
# NAME        : _delete_yppasswd
# DESCRIPTION : remove a user's entry from a passwd file
# REQUIRES    : string(login)
# RETURNS     : TRUE or FALSE
# NOTES       : we assume here that you've already checked to make sure the
#             : user you want to delete is a valid account in the file
# =============================================================================
sub _delete_yppasswd {
    my ($uname) = @_;
    my $name = "_delete_yppasswd";
    my ( $package, $filename, $line ) = caller;
    logmsg("$name: entering with args @_");
    logmsg("$name: called from package->$package, filename->$filename, line->$line");

    my $retval = 0;
    my $skip   = 0;

    # Read the entire current password file into %USERS
    my ( %USERS, %VAL );
    my $in;
    open( FI, "<$PASSWDFILE" );
    while ( chomp( $in = <FI> ) ) {
        my ( $user, $x, $x, $x, $x, $x, $x ) = split( /\:/, $in, 7 );
        $USERS{$user} = $in;    ## userid / user record ##
    }
    close FI;

    if ( !$skip ) {

        # Open the password file for append
        if ( open( FO, ">$PASSWDFILE" ) ) {
            logmsg("$name: opened password file for write (not append)");
        } else {
            logmsg("$name: FAILED to open password file for write ($!)");
            return(FALSE);
        }

        # Place an exclusive lock on the file
        if ( flock( FO, LOCK_EX ) ) {
            logmsg("$name: placed exclusive lock on password file");
        } else {
            logmsg("$name: FAILED to place lock on file ($!)");
            return(FALSE);
        }

        # write out the new passwd file, minus the account we're deleting
        foreach my $key ( keys(%USERS) ) {
            next if ( $key eq $uname );
            print FO "$USERS{$key}\n";
        }

        # unlock the file
        if ( flock( FO, LOCK_UN ) ) {
            logmsg("$name: removed lock from password file");
        } else {
            logmsg("$name: FAILED to remove lock from password file ($!)");
            return(FALSE);
        }

        # close the file
        if ( close(FO) ) {
            logmsg("$name: closed password file");
        } else {
            logmsg("$name: FAILED to close password file ($!)");
            return(FALSE);
        }

    }

    ( !$skip ) ? return(1) : return(0);
}

# =============================================================================
# NAME        : _change_password
# DESCRIPTION : change the user's password in the passwd file
# REQUIRES    : string(uname), string(encrpass)
# RETURNS     : TRUE or FALSE
# NOTES       : encrpass is a UNIX crypt() password
#             : set pwd.dofilebkp to 1 to backup the file before the change
# =============================================================================
sub _change_password {
    my ( $uname, $encrpass ) = @_;
    my $name = "_change_password";
    logmsg("$name: entering with uname->$uname, encpass->$encrpass");

    my $retval = 0;
    my $skip   = 0;

    my ( %USERS, %VAL );

    open( FI, "<$PASSWDFILE" );
    my $in;
    while ( chomp( $in = <FI> ) ) {
        my ( $user, $pass, $valA, $valB, $valC, $valD, $valE ) =
          split( /\:/, $in, 7 );
        my $string = "$valA" . ':' . "$valB" . ':' . "$valC" . ':' . "$valD" . ':' . "$valE";
        $USERS{$user} = $pass;     ## userid / password "encrypted"
        $VAL{$user}   = $string;
    }
    close FI;

    if ( exists $USERS{$uname} ) {
        $USERS{$uname} = $encrpass;
        $retval = 1;
    } else {
        logmsg("$name: FAILED to change password, user $uname DNE in passwd file");
        $skip = 1;
    }
    if ( !$skip ) {
        open( FO, ">$PASSWDFILE" );        # prod #
        flock( FO, LOCK_EX );              # put an exclusive lock on the file
        foreach my $user ( keys(%USERS) ) {
            my $rec = "$user" . ':' . $USERS{$user} . ':' . "$VAL{$user}";
            print FO "$rec\n";
        }
        flock( FO, LOCK_UN );              # remove the file lock
        close FO;
        logmsg("$name: changed password for $uname");
    }

    ( !$skip ) ? return $retval : return $retval;

}


# =============================================================================
# NAME        : _lockUNIXPassword
# DESCRIPTION :
# REQUIRES    :
# RETURNS     :
# NOTES       :
# =============================================================================
sub _lockUNIXPassword {
    my ($uname) = @_;

    my $retval = 0;
    my $skip   = 0;

    my ( %USERS, %VAL );
    open( FI, "<$PASSWDFILE" );
    my $in;
    while ( chomp( $in = <FI> ) ) {
        my ( $user, $pass, $valA, $valB, $valC, $valD, $valE ) =
          split( /\:/, $in, 7 );
        my $string = "$valA" . ':' . "$valB" . ':' . "$valC" . ':' . "$valD" . ':' . "$valE";
        $USERS{$user} = $pass;     ## userid / password "encrypted"
        $VAL{$user}   = $string;
    }
    close FI;
    if ( exists $USERS{$uname} ) {
        my $tmppass = $USERS{$uname};
        if ( $tmppass =~ /^\*/ ) {
            logmsg("change password: user account already locked no action taken");
        } else {
            $USERS{$uname} = '*' . "$tmppass";    ## LOCK IT ##
        }
        $retval = 1;
    } else {
        logmsg("ABORTING CHANGE: user ID: $uname DNE in passwd file");
        $skip = 1;
    }

    if ( !$skip ) {
        open( FO, ">$PASSWDFILE" );           # prod #
        foreach my $user ( keys(%USERS) ) {
            my $rec = "$user" . ':' . $USERS{$user} . ':' . "$VAL{$user}";
            print FO "$rec\n";
        }
        close FO;
    }

    ( !$skip ) ? return $retval : return $retval;
}


# =============================================================================
# NAME        : _unlockUNIXPassword
# DESCRIPTION :
# REQUIRES    :
# RETURNS     :
# NOTES       :
# =============================================================================
sub _unlockUNIXPassword {
    my ($uname) = @_;

    my $retval = 0;
    my $skip   = 0;

    my ( %USERS, %VAL );
    open( FI, "<$PASSWDFILE" );
    my $in;
    while ( chomp( $in = <FI> ) ) {
        my ( $user, $pass, $valA, $valB, $valC, $valD, $valE ) =
          split( /\:/, $in, 7 );
        my $string = "$valA" . ':' . "$valB" . ':' . "$valC" . ':' . "$valD" . ':' . "$valE";
        $USERS{$user} = $pass;     ## userid / password "encrypted"
        $VAL{$user}   = $string;
    }
    close FI;
    if ( exists $USERS{$uname} ) {
        if ( $USERS{$uname} =~ /^\*/ ) {
            $USERS{$uname} =~ s/^\*+//;
        } else {
            logmsg("ABORTING CHANGE: user ID: $uname is currently unlocked");
        }
        $retval = 1;
    } else {
        logmsg("ABORTING CHANGE: user ID: $uname DNE in passwd file");
        $skip = 1;
    }

    if ( !$skip ) {
        open( FO, ">$PASSWDFILE" );    # prod #
        foreach my $user ( keys(%USERS) ) {
            my $rec = "$user" . ':' . $USERS{$user} . ':' . "$VAL{$user}";
            print FO "$rec\n";
        }
        close FO;
    }

    ( !$skip ) ? return $retval : return $retval;
}

1;
